﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BattleAtms.Models;
using BattleAtms.Helpers;

namespace BattleAtms.Models
{
    class Game : IGame
    {
        private static Game _me = new Game();

        public static IGame GetInstance()
        {
            return _me;
        }

        public Game()
        {
            _Users = new List<UserInfo>();
            _Games = new List<GameInfo>();
        }

        public int RegisterUser(string name)
        {
            foreach (UserInfo tempuser in _Users)
            {
                if (tempuser.Name == name)
                {
                    throw new Exception("User name " + name + " is already registered");
                }
            }

            UserInfo user = new UserInfo();
            user.Name = name;
            user.Id = ++_UserIdSequence;

            _Users.Add(user);

            return user.Id;
        }

        public GameState GetGameState(int userId)
        {
            return GetUserById(userId).GameState;
        }

        public ICollection<Ship> GetShips(int userId)
        {
            foreach (GameInfo game in _Games)
            {
                if ((game.User.Id == userId || game.Oponent.Id == userId) &&
                    game.GameState != GameState.GameOver)
                {
                    if (game.User.Id == userId)
                    {
                        return game.UserShips;
                    }
                    else if (game.Oponent.Id == userId)
                    {
                        return game.OponentShips;
                    }
                }
            }

            return new List<Ship>();
        }

        public ICollection<UserInfo> GetUsers(int userId)
        {
            ICollection<UserInfo> waitingUsers = new List<UserInfo>();

            foreach (UserInfo user in _Users)
            {
                if (user.Id == userId)
                    continue;

                bool isUserWait = true;

                foreach (GameInfo game in _Games)
                {
                    if (game.User.Id == user.Id && game.User.GameState != GameState.GameOver)
                    {
                        isUserWait = false;
                        break;
                    }
                    else if (game.Oponent.Id == user.Id && game.Oponent.GameState != GameState.GameOver)
                    {
                        isUserWait = false;
                        break;
                    }
                }

                if (isUserWait)
                {
                    waitingUsers.Add(user);
                }
            }

            return waitingUsers;
        }

        public CellState DoTurn(int userId, int x, int y)
        {
            if (x > _FileldWidth || x < 0 || y > _FileldHeight || y < 0)
                throw new Exception("Incorrect coordinates value");

            foreach (GameInfo game in _Games)
            {
                if ((game.User.Id == userId || game.Oponent.Id == userId) &&
                     game.GameState != GameState.GameOver)
                {
                    if (game.User.Id == userId)
                    {
                        //check is cell belong to ship
                        //set ship's cell state
                        //updat ship's state
                        //update cells state

                        if (IsCellBelongToShip(game.OponentShips, x, y))
                        {
                            SetShipState(game.OponentShips, CellState.Hit, x, y);                         
                            UpdateCell(game.OponentShips, game.OponentCells);

                            if (IsGameOver(game.OponentShips))
                            {
                                game.GameState = GameState.GameOver;
                                game.User.GameState = GameState.GameOver;
                                game.Oponent.GameState = GameState.GameOver;
                            }
                            else
                            {                                
                                game.User.GameState = GameState.MyTurn;
                                game.Oponent.GameState = GameState.OpponentsTurn;
                            }
                        }
                        else
                        {
                            game.OponentCells[x, y].State = CellState.Opened;

                            game.User.GameState = GameState.OpponentsTurn;
                            game.Oponent.GameState = GameState.MyTurn;
                        }

                        return game.OponentCells[x, y].State;
                    }
                    else
                    {
                        if (IsCellBelongToShip(game.UserShips, x, y))
                        {
                            SetShipState(game.UserShips, CellState.Hit, x, y);
                            UpdateCell(game.UserShips, game.UserCells);

                            if (IsGameOver(game.OponentShips))
                            {
                                game.GameState = GameState.GameOver;
                                game.User.GameState = GameState.GameOver;
                                game.Oponent.GameState = GameState.GameOver;
                            }
                            else
                            {
                                game.User.GameState = GameState.OpponentsTurn;
                                game.Oponent.GameState = GameState.MyTurn;
                            }
                        }
                        else
                        {
                            game.UserCells[x, y].State = CellState.Opened;

                            game.User.GameState = GameState.MyTurn;
                            game.Oponent.GameState = GameState.OpponentsTurn;
                        }

                        return game.UserCells[x, y].State;
                    }
                }
            }

            throw new Exception("User " + userId.ToString() + " is not participate in the game");
        }

        public void StartGame(int userId, int opponentId)
        {
            IndexModel model = new IndexModel();

            GameInfo game = new GameInfo();
            game.GameState = GameState.Initial;
            game.User = GetUserById(userId);
            game.Oponent = GetUserById(opponentId);
            game.User.GameState = GameState.MyTurn;
            game.Oponent.GameState = GameState.OpponentsTurn;
            
            game.UserCells = new Cell[_FileldHeight, _FileldWidth];
            for (int xidx = 0; xidx < _FileldWidth; ++xidx)
            {
                for (int yidx = 0; yidx < _FileldHeight; ++yidx)
                {
                    game.UserCells[xidx, yidx] = new Cell();
                    game.UserCells[xidx, yidx].X = xidx;
                    game.UserCells[xidx, yidx].Y = yidx;
                }
            }

            game.OponentCells = new Cell[_FileldHeight, _FileldWidth];
            for (int xidx = 0; xidx < _FileldWidth; ++xidx)
            {
                for (int yidx = 0; yidx < _FileldHeight; ++yidx)
                {
                    game.OponentCells[xidx, yidx] = new Cell();
                    game.OponentCells[xidx, yidx].X = xidx;
                    game.OponentCells[xidx, yidx].Y = yidx;
                }
            }


            game.UserShips = GameHelper.GenerateShips();
            game.OponentShips = GameHelper.GenerateShips();

            UpdateCell(game.UserShips, game.UserCells);
            UpdateCell(game.OponentShips, game.OponentCells);

            _Games.Add(game);
        }

        public int? GetOpponentId(int userId)
        {
            foreach (GameInfo game in _Games)
            {
                if ((game.User.Id == userId || game.Oponent.Id == userId) &&
                    game.GameState != GameState.GameOver)
                {
                    if (game.User.Id == userId)
                        return game.Oponent.Id;
                    else
                        return game.User.Id;
                }
            }

            return null;
        }

        public CellState[,] GetCells(int userId)
        {
            foreach (GameInfo game in _Games)
            {
                if ((game.User.Id == userId || game.Oponent.Id == userId) &&
                    game.GameState != GameState.GameOver)
                {
                    if (game.User.Id == userId)
                    {
                        return CellsToList(game.UserCells);
                    }
                    else
                    {
                        return CellsToList(game.OponentCells);
                    }
                }
            }

            throw new Exception("User " + userId.ToString() + " is not participate in the game");
        }

        private bool IsCellBelongToShip(ICollection<Ship> ships, int x, int y)
        {
            bool isBelongToShip = false;

            foreach (Ship ship in ships)
            {
                foreach (Cell cell in ship.Decks)
                {
                    if (cell.X == x && cell.Y == y)
                    {
                        isBelongToShip = true;
                        break;
                    }
                }
            }

            return isBelongToShip;
        }

        private void SetShipState(ICollection<Ship> ships, CellState state, int x, int y)
        {
            foreach (Ship ship in ships)
            {
                foreach (Cell cell in ship.Decks)
                {
                    if (cell.X == x && cell.Y == y)
                    {
                        cell.State = state;
                        UpdateShipState(ship);
                        goto end;
                    }
                }
            }

        end: ;
        }

        private void UpdateShipState(Ship ship)
        {
            CellState state = CellState.Empty;

            foreach (Cell cell in ship.Decks)
            {
                if (cell.State != CellState.Hit && cell.State != CellState.Dead)
                {
                    if (state == CellState.Hit)
                        state = cell.State;
                }
                else
                {
                    state = CellState.Filled;
                    break;
                }
            }

            if (state == CellState.Hit)
            {
                foreach (Cell cell in ship.Decks)
                {
                    cell.State = CellState.Dead;
                }
            }
        }

        private void UpdateCell(ICollection<Ship> ships, Cell[,] cells)
        {
            foreach (Ship ship in ships)
            {
                foreach (Cell cell in ship.Decks)
                {
                    cells[cell.X, cell.Y].State = cell.State;
                }
            }
        }

        private CellState[,] CellsToList(Cell[,] cells)
        {
            //List<Cell> cellsList = new List<Cell>();
            CellState[,] cellsList = new CellState[_FileldWidth, _FileldHeight];

            for (int xidx = 0; xidx < _FileldWidth; ++xidx)
            {
                for (int yidx = 0; yidx < _FileldHeight; ++yidx)
                { 
                    cellsList[xidx, yidx] = cells[xidx, yidx].State;
                }
            }

            return cellsList;
        }

        private bool IsGameOver(ICollection<Ship> ships)
        {
            bool isGameOver = true;

            foreach (Ship ship in ships)
            {
                foreach (Cell cell in ship.Decks)
                {
                    if (cell.State != CellState.Dead || cell.State != CellState.Hit)
                    {
                        isGameOver = false;
                        goto end;
                    }
                }
            }

        end: ;

        return isGameOver;
        }

        private UserInfo GetUserById(int id)
        {
            foreach (UserInfo user in _Users)
            {
                if (user.Id == id)
                    return user;
            }

            throw new Exception("User is not exists");
        }

        private int _UserIdSequence;
        private ICollection<UserInfo> _Users;
        private ICollection<GameInfo> _Games;
        private const int _FileldWidth = 10;
        private const int _FileldHeight = 10;
    }
}
